package manager

import (
	"context"
	"github.com/stretchr/testify/assert"
	"gitverse.ru/synapse/kubelatte/pkg/logs"
	"gitverse.ru/synapse/kubelatte/pkg/operator/controllers"
	"gitverse.ru/synapse/kubelatte/pkg/util"
	v1 "k8s.io/api/authorization/v1"
	"k8s.io/apimachinery/pkg/runtime"
	"k8s.io/client-go/kubernetes"
	"k8s.io/client-go/kubernetes/fake"
	testing2 "k8s.io/client-go/testing"
	"testing"
	"time"
)

func TestGetPermissons(t *testing.T) {

	defer func(validator, mutator, creator string) {
		controllers.KbltValidator = validator
		controllers.KbltMutator = mutator
		controllers.KbltCreator = creator
	}(controllers.KbltValidator, controllers.KbltMutator, controllers.KbltCreator)
	var validatorPerms = []permission{
		{
			Verbs:     []string{"get", "list", "watch", "create", "update", "watch", "delete", "patch"},
			Group:     "kubelatte.io",
			Resources: []string{"scopes/status", "templates/status"},
			Namespace: controllers.OperatorNamespace,
		},
		{
			Verbs:     []string{"get", "list", "watch", "create", "update", "watch", "delete", "patch"},
			Group:     "coordination.k8s.io",
			Resources: []string{"leases"},
			Namespace: controllers.OperatorNamespace,
		},
		{
			Verbs:     []string{"get", "list", "watch"},
			Group:     "kubelatte.io",
			Resources: []string{"scopes", "templates"},
			Namespace: controllers.OperatorNamespace,
		},
		{
			Verbs:     []string{"get", "list", "watch"},
			Group:     "",
			Resources: []string{"namespaces"},
			Namespace: "",
		},
	}
	var mutatorPerms = []permission{
		{
			Verbs:     []string{"get", "list", "watch", "create", "update", "watch", "delete", "patch"},
			Group:     "kubelatte.io",
			Resources: []string{"scopes/status", "templates/status", "triggers/status"},
			Namespace: controllers.OperatorNamespace,
		},
		{
			Verbs:     []string{"get", "list", "watch", "create", "update", "watch", "delete", "patch"},
			Group:     "coordination.k8s.io",
			Resources: []string{"leases"},
			Namespace: controllers.OperatorNamespace,
		},
		{
			Verbs:     []string{"get", "list", "watch"},
			Group:     "kubelatte.io",
			Resources: []string{"scopes", "templates", "triggers"},
			Namespace: controllers.OperatorNamespace,
		},
		{
			Verbs:     []string{"get", "list", "watch"},
			Group:     "",
			Resources: []string{"namespaces"},
			Namespace: "",
		},
	}
	var creatorPerms = []permission{
		{
			Verbs:     []string{"get", "list", "watch"},
			Group:     "kubelatte.io",
			Resources: []string{"triggerinstances"},
			Namespace: "",
		},
		{
			Verbs:     []string{"get", "list", "watch", "create", "update", "watch", "delete", "patch"},
			Group:     "kubelatte.io",
			Resources: []string{"triggerinstances/status"},
			Namespace: "",
		},
		{
			Verbs:     []string{"get", "list", "watch", "create", "update", "watch", "delete", "patch"},
			Group:     "kubelatte.io",
			Resources: []string{"scopes/status", "templates/status", "triggers/status"},
			Namespace: controllers.OperatorNamespace,
		},
		{
			Verbs:     []string{"get", "list", "watch", "create", "update", "watch", "delete", "patch"},
			Group:     "coordination.k8s.io",
			Resources: []string{"leases"},
			Namespace: controllers.OperatorNamespace,
		},
		{
			Verbs:     []string{"get", "list", "watch"},
			Group:     "kubelatte.io",
			Resources: []string{"scopes", "templates", "triggers"},
			Namespace: controllers.OperatorNamespace,
		},
		{
			Verbs:     []string{"get", "list", "watch"},
			Group:     "",
			Resources: []string{"namespaces"},
			Namespace: "",
		},
	}
	var sideeffectPerms = []permission{
		{
			Verbs:     []string{"get", "list", "watch"},
			Group:     "kubelatte.io",
			Resources: []string{"triggerinstances"},
			Namespace: "",
		},
		{
			Verbs:     []string{"get", "list", "watch", "create", "update", "watch", "delete", "patch"},
			Group:     "kubelatte.io",
			Resources: []string{"triggerinstances/status"},
			Namespace: "",
		},
		{
			Verbs:     []string{"create"},
			Group:     "kubelatte.io",
			Resources: []string{"triggerinstances"},
			Namespace: "",
		},
		{
			Verbs:     []string{"get", "list", "watch", "create", "update", "watch", "delete", "patch"},
			Group:     "kubelatte.io",
			Resources: []string{"scopes/status", "templates/status", "triggers/status"},
			Namespace: controllers.OperatorNamespace,
		},
		{
			Verbs:     []string{"get", "list", "watch", "create", "update", "watch", "delete", "patch"},
			Group:     "coordination.k8s.io",
			Resources: []string{"leases"},
			Namespace: controllers.OperatorNamespace,
		},
		{
			Verbs:     []string{"get", "list", "watch"},
			Group:     "kubelatte.io",
			Resources: []string{"scopes", "templates", "triggers"},
			Namespace: controllers.OperatorNamespace,
		},
		{
			Verbs:     []string{"get", "list", "watch"},
			Group:     "",
			Resources: []string{"namespaces"},
			Namespace: "",
		},
	}
	t.Run("validator", func(t *testing.T) {
		p := &PermissionManager{}
		controllers.KbltValidator = "true"
		controllers.KbltMutator = "false"
		controllers.KbltCreator = "false"
		controllers.KbltSideEffect = "false"
		assert.Equalf(t, validatorPerms, p.getPermissions(), "getPermissions()")
	})
	t.Run("mutator", func(t *testing.T) {
		p := &PermissionManager{}
		controllers.KbltValidator = "true"
		controllers.KbltMutator = "true"
		controllers.KbltCreator = "false"
		controllers.KbltSideEffect = "false"
		assert.Equalf(t, mutatorPerms, p.getPermissions(), "getPermissions()")
	})
	t.Run("creator", func(t *testing.T) {
		p := &PermissionManager{}
		controllers.KbltValidator = "false"
		controllers.KbltMutator = "true"
		controllers.KbltCreator = "true"
		controllers.KbltSideEffect = "false"
		assert.Equalf(t, creatorPerms, p.getPermissions(), "getPermissions()")
	})
	t.Run("sideeffects", func(t *testing.T) {
		p := &PermissionManager{}
		controllers.KbltValidator = "true"
		controllers.KbltMutator = "true"
		controllers.KbltCreator = "true"
		controllers.KbltSideEffect = "true"
		assert.Equalf(t, sideeffectPerms, p.getPermissions(), "getPermissions()")
	})

}

func TestPermissionManager_CheckPermissions(t *testing.T) {
	type fields struct {
		ticker         *time.Ticker
		triggersExist  bool
		templatesExist bool
		scopesExist    bool
		logOnly        bool
	}
	client1 := fake.NewSimpleClientset()
	client1.PrependReactor(
		"create",
		"selfsubjectaccessreviews",
		func(action testing2.Action) (handled bool, ret runtime.Object, err error) {
			return true, &v1.SelfSubjectAccessReview{
				Status: v1.SubjectAccessReviewStatus{
					Allowed:         true,
					Denied:          false,
					Reason:          "cause we can",
					EvaluationError: "",
				},
			}, nil
		},
	)
	client2 := fake.NewSimpleClientset()
	client2.PrependReactor(
		"create",
		"selfsubjectaccessreviews",
		func(action testing2.Action) (handled bool, ret runtime.Object, err error) {
			return true, &v1.SelfSubjectAccessReview{
				Status: v1.SubjectAccessReviewStatus{
					Allowed:         false,
					Denied:          true,
					Reason:          "forbidden for current user",
					EvaluationError: "",
				},
			}, nil
		},
	)
	type args struct {
		ctx    context.Context
		client kubernetes.Interface
	}
	tests := []struct {
		name    string
		fields  fields
		args    args
		wantErr bool
	}{
		{
			name:   "allowed",
			fields: fields{},
			args: args{
				ctx:    context.Background(),
				client: client1,
			},
			wantErr: false,
		},
		{
			name:   "forbidden",
			fields: fields{},
			args: args{
				ctx:    context.Background(),
				client: client2,
			},
			wantErr: true,
		},
		{
			name: "log only",
			fields: fields{
				logOnly: true,
			},
			args: args{
				ctx:    context.Background(),
				client: client2,
			},
			wantErr: false,
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			m := &PermissionManager{
				ticker:  tt.fields.ticker,
				client:  tt.args.client,
				logOnly: tt.fields.logOnly,
			}
			logs.LogLevel = "debug"
			logs.LogsPath = "./"
			logs.LogsFileName = "test.log"
			logs.LoggersInit()

			err := m.checkPermissionsAndSetStatus(tt.args.ctx)
			if (err != nil) != tt.wantErr {
				t.Errorf("checkPermissions() error = %v, wantErr %v", err, tt.wantErr)
			}
			if err != nil {
				t.Log(err)
			}

		})
	}
}

func TestInitPermissionManager(t *testing.T) {
	type args struct {
		cl    kubernetes.Interface
		check string
	}
	tests := []struct {
		name string
		args args
		want *PermissionManager
	}{
		{
			name: "nil",
			args: args{
				check: "false",
				cl:    nil,
			},
			want: nil,
		},
		{
			name: "normal start",
			args: args{
				check: "true",
				cl:    &kubernetes.Clientset{},
			},
			want: &PermissionManager{
				checkPeriod: time.Minute * 3,
				client:      &kubernetes.Clientset{},
			},
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			util.KbltPermissionsCheck = tt.args.check
			assert.Equalf(t, tt.want, InitPermissionManager(tt.args.cl), "InitPermissionManager(%v)", tt.args.cl)
		})
	}
}

func TestPermissionManager_stop(t *testing.T) {
	type fields struct {
		startOnly   bool
		logOnly     bool
		checkPeriod time.Duration
		ticker      *time.Ticker
		permissons  []permission
		client      kubernetes.Interface
	}
	type args struct {
		ctx context.Context
	}
	tests := []struct {
		name   string
		fields fields
		args   args
	}{
		{
			name: "not nil",
			fields: fields{
				startOnly:   false,
				logOnly:     false,
				checkPeriod: 0,
				ticker:      time.NewTicker(time.Second),
				permissons:  nil,
				client:      nil,
			},
			args: args{
				ctx: context.Background(),
			},
		},
		{
			name: "nil",
			fields: fields{
				startOnly:   false,
				logOnly:     false,
				checkPeriod: 0,
				ticker:      nil,
				permissons:  nil,
				client:      nil,
			},
			args: args{
				ctx: context.Background(),
			},
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			p := &PermissionManager{
				startOnly:   tt.fields.startOnly,
				logOnly:     tt.fields.logOnly,
				checkPeriod: tt.fields.checkPeriod,
				ticker:      tt.fields.ticker,
				permissons:  tt.fields.permissons,
				client:      tt.fields.client,
			}
			p.stop(tt.args.ctx)
		})
	}
}

func TestPermissionManager_startPermissionWatcher(t *testing.T) {
	client1 := fake.NewSimpleClientset()
	client1.PrependReactor(
		"create",
		"selfsubjectaccessreviews",
		func(action testing2.Action) (handled bool, ret runtime.Object, err error) {
			return true, &v1.SelfSubjectAccessReview{
				Status: v1.SubjectAccessReviewStatus{
					Allowed:         true,
					Denied:          false,
					Reason:          "cause we can",
					EvaluationError: "",
				},
			}, nil
		},
	)
	type fields struct {
		startOnly   bool
		logOnly     bool
		checkPeriod time.Duration
		ticker      *time.Ticker
		permissons  []permission
		client      kubernetes.Interface
	}
	type args struct {
		ctx context.Context
	}
	tests := []struct {
		name   string
		fields fields
		args   args
	}{
		{
			name: "",
			fields: fields{
				startOnly:   false,
				logOnly:     false,
				checkPeriod: time.Second,
				ticker:      nil,
				permissons:  []permission{},
				client:      client1,
			},
			args: args{
				ctx: context.Background(),
			},
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			p := &PermissionManager{
				startOnly:   tt.fields.startOnly,
				logOnly:     tt.fields.logOnly,
				checkPeriod: tt.fields.checkPeriod,
				ticker:      tt.fields.ticker,
				permissons:  tt.fields.permissons,
				client:      tt.fields.client,
			}
			ctx, cancel := context.WithCancel(tt.args.ctx)
			go func() {
				time.Sleep(3 * time.Second)
				cancel()
			}()
			p.startPermissionWatcher(ctx)

		})
	}
}

func TestPermissionManager_checkPermsForFirstTime(t *testing.T) {
	type fields struct {
		startOnly   bool
		logOnly     bool
		checkPeriod time.Duration
		ticker      *time.Ticker
		permissons  []permission
		client      kubernetes.Interface
	}
	type args struct {
		ctx context.Context
	}
	client1 := fake.NewSimpleClientset()
	client1.PrependReactor("create",
		"selfsubjectaccessreviews",
		func(action testing2.Action) (handled bool, ret runtime.Object, err error) {
			return true, &v1.SelfSubjectAccessReview{
				Status: v1.SubjectAccessReviewStatus{
					Allowed:         false,
					Denied:          false,
					Reason:          "cause we cant",
					EvaluationError: "",
				},
			}, nil
		})
	client2 := fake.NewSimpleClientset()
	client2.PrependReactor("create",
		"selfsubjectaccessreviews",
		func(action testing2.Action) (handled bool, ret runtime.Object, err error) {
			return true, &v1.SelfSubjectAccessReview{
				Status: v1.SubjectAccessReviewStatus{
					Allowed:         true,
					Denied:          false,
					Reason:          "cause we can",
					EvaluationError: "",
				},
			}, nil
		})
	tests := []struct {
		name   string
		fields fields
		args   args
	}{
		{
			name: "test",
			fields: fields{
				startOnly:   false,
				logOnly:     false,
				checkPeriod: time.Second,
				ticker:      nil,
				permissons: []permission{{
					Verbs:     []string{"test"},
					Group:     "",
					Resources: []string{"test"},
					Version:   "",
					Namespace: "",
				}},
				client: client1,
			},
			args: args{},
		},
	}
	logs.LogLevel = "debug"
	logs.LogsPath = "./"
	logs.LogsFileName = "test.log"
	logs.LoggersInit()

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			p := &PermissionManager{
				startOnly:   tt.fields.startOnly,
				logOnly:     tt.fields.logOnly,
				checkPeriod: tt.fields.checkPeriod,
				ticker:      tt.fields.ticker,
				permissons:  tt.fields.permissons,
				client:      tt.fields.client,
			}
			go func() {
				time.Sleep(time.Millisecond * 150)
				p.client = client2
			}()
			p.checkPermsForFirstTime(tt.args.ctx)
		})
	}
}

func TestPermissionManager_StartCheck(t *testing.T) {
	type fields struct {
		startOnly   bool
		logOnly     bool
		checkPeriod time.Duration
		ticker      *time.Ticker
		permissons  []permission
		client      kubernetes.Interface
	}
	tests := []struct {
		name   string
		fields fields
	}{
		{
			name: "tst",
			fields: fields{
				checkPeriod: time.Millisecond,
				permissons:  []permission{},
			},
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			p := &PermissionManager{
				startOnly:   tt.fields.startOnly,
				logOnly:     tt.fields.logOnly,
				checkPeriod: tt.fields.checkPeriod,
				ticker:      tt.fields.ticker,
				permissons:  tt.fields.permissons,
				client:      tt.fields.client,
			}
			p.StartCheck()
		})
	}
	t.Run("nil", func(t *testing.T) {
		var p *PermissionManager = nil
		p.StartCheck()
	})
}
